<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>你真的了解回流和重绘吗 | 砖业洋__</title>
    <meta name="generator" content="VuePress 1.5.2">
    <link rel="icon" href="/haizei.jpg">
    <meta name="description" content="">
    <link rel="preload" href="/assets/css/0.styles.1f351a1e.css" as="style"><link rel="preload" href="/assets/js/app.1c226385.js" as="script"><link rel="preload" href="/assets/js/2.5bd328db.js" as="script"><link rel="preload" href="/assets/js/3.90e623d3.js" as="script"><link rel="preload" href="/assets/js/6.db1982cd.js" as="script"><link rel="prefetch" href="/assets/js/10.4eb1cc28.js"><link rel="prefetch" href="/assets/js/11.cd070e67.js"><link rel="prefetch" href="/assets/js/12.0c5442fd.js"><link rel="prefetch" href="/assets/js/4.3a00ff1b.js"><link rel="prefetch" href="/assets/js/5.9f70a21c.js"><link rel="prefetch" href="/assets/js/7.46572acb.js"><link rel="prefetch" href="/assets/js/8.2b398bc4.js"><link rel="prefetch" href="/assets/js/9.f2979413.js">
    <link rel="stylesheet" href="/assets/css/0.styles.1f351a1e.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><!----> <!----> <header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><img src="/haizei.jpg" alt="砖业洋__" class="logo"> <span class="site-name can-hide">砖业洋__</span></a> <div class="links"><span>你好，砖业洋__持续为你分享    </span> <div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/" class="nav-link">
  首页
</a></div><div class="nav-item"><a href="https://liuchenyang0515.blog.csdn.net" target="_blank" rel="noopener noreferrer" class="nav-link external">
  CSDN主页
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div> <!----></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/" class="nav-link">
  首页
</a></div><div class="nav-item"><a href="https://liuchenyang0515.blog.csdn.net" target="_blank" rel="noopener noreferrer" class="nav-link external">
  CSDN主页
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div> <!----></nav>  <ul class="sidebar-links"><li><section class="sidebar-group depth-0"><p class="sidebar-heading open"><span>你真的了解回流和重绘吗</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#浏览器的渲染过程" class="sidebar-link">浏览器的渲染过程</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#生成渲染树" class="sidebar-link">生成渲染树</a></li><li class="sidebar-sub-header"><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#回流" class="sidebar-link">回流</a></li><li class="sidebar-sub-header"><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#重绘" class="sidebar-link">重绘</a></li></ul></li><li><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#何时发生回流重绘" class="sidebar-link">何时发生回流重绘</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#浏览器的优化机制" class="sidebar-link">浏览器的优化机制</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#减少回流和重绘" class="sidebar-link">减少回流和重绘</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#最小化重绘和重排" class="sidebar-link">最小化重绘和重排</a></li><li class="sidebar-sub-header"><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#批量修改dom" class="sidebar-link">批量修改DOM</a></li><li class="sidebar-sub-header"><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#避免触发同步布局事件" class="sidebar-link">避免触发同步布局事件</a></li><li class="sidebar-sub-header"><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#对于复杂动画效果-使用绝对定位让其脱离文档流" class="sidebar-link">对于复杂动画效果,使用绝对定位让其脱离文档流</a></li><li class="sidebar-sub-header"><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#css3硬件加速（gpu加速）" class="sidebar-link">css3硬件加速（GPU加速）</a></li></ul></li><li><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#总结" class="sidebar-link">总结</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/tech/nizhendeliaojiehuiliuhechonghuima.html#参考文献" class="sidebar-link">参考文献</a><ul class="sidebar-sub-headers"></ul></li></ul></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><h1 id="你真的了解回流和重绘吗"><a href="#你真的了解回流和重绘吗" class="header-anchor">#</a> 你真的了解回流和重绘吗</h1> <blockquote><p>作者：chenjigeng</p></blockquote> <p>回流和重绘可以说是每一个web开发者都经常听到的两个词语，我也不例外，可是我之前一直不是很清楚这两步具体做了什么事情。最近由于部门内部要做分享，所以对其进行了一些研究，看了一些博客和书籍，整理了一些内容并且结合一些例子，写了这篇文章，希望可以帮助到大家。</p> <h2 id="浏览器的渲染过程"><a href="#浏览器的渲染过程" class="header-anchor">#</a> 浏览器的渲染过程</h2> <p>本文先从浏览器的渲染过程来从头到尾的讲解一下回流重绘，如果大家想直接看如何减少回流和重绘，可以跳到后面。（这个渲染过程来自<a href="https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=zh-cn" target="_blank" rel="noopener noreferrer">MDN<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>）</p> <p><img src="/assets/img/dom_render.89b6070a.png" alt="DOM渲染"></p> <p>从上面这个图上，我们可以看到，浏览器渲染过程如下：</p> <ol><li>解析HTML，生成DOM树，解析CSS，生成CSSOM树</li> <li>将DOM树和CSSOM树结合，生成渲染树(Render Tree)</li> <li>Layout(回流):根据生成的渲染树，进行回流(Layout)，得到节点的几何信息（位置，大小）</li> <li>Painting(重绘):根据渲染树以及回流得到的几何信息，得到节点的绝对像素</li> <li>Display:将像素发送给GPU，展示在页面上。（这一步其实还有很多内容，比如会在GPU将多个合成层合并为同一个层，并展示在页面中。而css3硬件加速的原理则是新建合成层，这里我们不展开，之后有机会会写一篇博客）</li></ol> <p>渲染过程看起来很简单，让我们来具体了解下每一步具体做了什么。</p> <h3 id="生成渲染树"><a href="#生成渲染树" class="header-anchor">#</a> 生成渲染树</h3> <p><img src="/assets/img/generate_render_tree.94866059.png" alt="生成渲染树"></p> <p>为了构建渲染树，浏览器主要完成了以下工作：</p> <ol><li>从DOM树的根节点开始遍历每个可见节点。</li> <li>对于每个可见的节点，找到CSSOM树中对应的规则，并应用它们。</li> <li>根据每个可见节点以及其对应的样式，组合生成渲染树。</li></ol> <p>第一步中，既然说到了要遍历可见的节点，那么我们得先知道，什么节点是不可见的。不可见的节点包括：</p> <ul><li>一些不会渲染输出的节点，比如script、meta、link等。</li> <li>一些通过css进行隐藏的节点。比如display:none。注意，利用visibility和opacity隐藏的节点，还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。</li></ul> <p><strong>注意：渲染树只包含可见的节点</strong></p> <h3 id="回流"><a href="#回流" class="header-anchor">#</a> 回流</h3> <p>前面我们通过构造渲染树，我们将可见DOM节点以及它对应的样式结合起来，可是我们还需要计算它们在设备视口(viewport)内的确切位置和大小，这个计算的阶段就是回流。</p> <p>为了弄清每个对象在网站上的确切大小和位置，浏览器从渲染树的根节点开始遍历，我们可以以下面这个实例来表示：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token doctype">&lt;!DOCTYPE html&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>viewport<span class="token punctuation">&quot;</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>width=device-width,initial-scale=1<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">&gt;</span></span>Critial Path: Hello world!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">=&quot;</span><span class="token attr-value"><span class="token property">width</span><span class="token punctuation">:</span> 50%</span><span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">=&quot;</span><span class="token attr-value"><span class="token property">width</span><span class="token punctuation">:</span> 50%</span><span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Hello world!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>我们可以看到，第一个div将节点的显示尺寸设置为视口宽度的50%，第二个div将其尺寸设置为父节点的50%。而在回流这个阶段，我们就需要根据视口具体的宽度，将其转为实际的像素值。（如下图）
<img src="" alt="渲染章节的viewport"></p> <h3 id="重绘"><a href="#重绘" class="header-anchor">#</a> 重绘</h3> <p>最终，我们通过构造渲染树和回流阶段，我们知道了哪些节点是可见的，以及可见节点的样式和具体的几何信息(位置、大小)，那么我们就可以将渲染树的每个节点都转换为屏幕上的实际像素，这个阶段就叫做重绘节点。</p> <p>既然知道了浏览器的渲染过程后，我们就来探讨下，何时会发生回流重绘。</p> <h2 id="何时发生回流重绘"><a href="#何时发生回流重绘" class="header-anchor">#</a> 何时发生回流重绘</h2> <p>我们前面知道了，回流这一阶段主要是计算节点的位置和几何信息，那么当页面布局和几何信息发生变化的时候，就需要回流。比如以下情况：</p> <ul><li>添加或删除可见的DOM元素</li> <li>元素的位置发生变化</li> <li>元素的尺寸发生变化（包括外边距、内边框、边框大小、高度和宽度等）</li> <li>内容发生变化，比如文本变化或图片被另一个不同尺寸的图片所替代。</li> <li>页面一开始渲染的时候（这肯定避免不了）</li> <li>浏览器的窗口尺寸变化（因为回流是根据视口的大小来计算元素的位置和大小的）</li></ul> <p><strong>注意：回流一定会触发重绘，而重绘不一定会回流</strong></p> <p>根据改变的范围和程度，渲染树中或大或小的部分需要重新计算，有些改变会触发整个页面的重排，比如，滚动条出现的时候或者修改了根节点。</p> <h2 id="浏览器的优化机制"><a href="#浏览器的优化机制" class="header-anchor">#</a> 浏览器的优化机制</h2> <p>现代的浏览器都是很聪明的，由于每次重排都会造成额外的计算消耗，因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里，直到过了一段时间或者操作达到了一个阈值，才清空队列。但是！<strong>当你获取布局信息的操作的时候，会强制队列刷新</strong>，比如当你访问以下属性或者使用以下方法：</p> <ul><li>offsetTop、offsetLeft、offsetWidth、offsetHeight</li> <li>scrollTop、scrollLeft、scrollWidth、scrollHeight</li> <li>clientTop、clientLeft、clientWidth、clientHeight</li> <li>getComputedStyle()</li> <li>getBoundingClientRect</li> <li>具体可以访问这个网站：<a href="https://gist.github.com/paulirish/5d52fb081b3570c81e3a" target="_blank" rel="noopener noreferrer">gist.github.com/paulirish/5…<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></li></ul> <p>以上属性和方法都需要返回最新的布局信息，因此浏览器不得不清空队列，触发回流重绘来返回正确的值。因此，我们在修改样式的时候，**最好避免使用上面列出的属性，他们都会刷新渲染队列。**如果要使用它们，最好将值缓存起来。</p> <h2 id="减少回流和重绘"><a href="#减少回流和重绘" class="header-anchor">#</a> 减少回流和重绘</h2> <p>好了，到了我们今天的重头戏，前面说了这么多背景和理论知识，接下来让我们谈谈如何减少回流和重绘。</p> <h3 id="最小化重绘和重排"><a href="#最小化重绘和重排" class="header-anchor">#</a> 最小化重绘和重排</h3> <p>由于重绘和重排可能代价比较昂贵，因此最好就是可以减少它的发生次数。为了减少发生次数，我们可以合并多次对DOM和样式的修改，然后一次处理掉。考虑这个例子</p> <div class="language-javascript extra-class"><pre class="language-javascript"><code><span class="token keyword">const</span> el <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'test'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>padding <span class="token operator">=</span> <span class="token string">'5px'</span><span class="token punctuation">;</span>
el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>borderLeft <span class="token operator">=</span> <span class="token string">'1px'</span><span class="token punctuation">;</span>
el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>borderRight <span class="token operator">=</span> <span class="token string">'2px'</span><span class="token punctuation">;</span>
</code></pre></div><p>例子中，有三个样式属性被修改了，每一个都会影响元素的几何结构，引起回流。当然，大部分现代浏览器都对其做了优化，因此，只会触发一次重排。但是如果在旧版的浏览器或者在上面代码执行的时候，有其他代码访问了布局信息(上文中的会触发回流的布局信息)，那么就会导致三次重排。</p> <p>因此，我们可以合并所有的改变然后依次处理，比如我们可以采取以下的方式：</p> <ul><li>使用cssText</li></ul> <div class="language-javascript extra-class"><pre class="language-javascript"><code><span class="token keyword">const</span> el <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'test'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>cssText <span class="token operator">+=</span> <span class="token string">'border-left: 1px; border-right: 2px; padding: 5px;'</span><span class="token punctuation">;</span>
</code></pre></div><ul><li>修改CSS的class</li></ul> <div class="language-javascript extra-class"><pre class="language-javascript"><code><span class="token keyword">const</span> el <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'test'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
el<span class="token punctuation">.</span>className <span class="token operator">+=</span> <span class="token string">' active'</span><span class="token punctuation">;</span>
</code></pre></div><h3 id="批量修改dom"><a href="#批量修改dom" class="header-anchor">#</a> 批量修改DOM</h3> <p>当我们需要对DOM对一系列修改的时候，可以通过以下步骤减少回流重绘次数：</p> <ol><li>使元素脱离文档流</li> <li>对其进行多次修改</li> <li>将元素带回到文档中。</li></ol> <p>该过程的第一步和第三步可能会引起回流，但是经过第一步之后，对DOM的所有修改都不会引起回流，因为它已经不在渲染树了。</p> <p>有三种方式可以让DOM脱离文档流：</p> <ul><li>隐藏元素，应用修改，重新显示</li> <li>使用文档片段(document fragment)在当前DOM之外构建一个子树，再把它拷贝回文档。</li> <li>将原始元素拷贝到一个脱离文档的节点中，修改节点后，再替换原始的元素。</li></ul> <p>考虑我们要执行一段批量插入节点的代码：</p> <div class="language-javascript extra-class"><pre class="language-javascript"><code><span class="token keyword">function</span> <span class="token function">appendDataToElement</span><span class="token punctuation">(</span><span class="token parameter">appendToElement<span class="token punctuation">,</span> data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">let</span> li<span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> data<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    	li <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'li'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        li<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">'text'</span><span class="token punctuation">;</span>
        appendToElement<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>li<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> ul <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'list'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">appendDataToElement</span><span class="token punctuation">(</span>ul<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>如果我们直接这样执行的话，由于每次循环都会插入一个新的节点，会导致浏览器回流一次。</p> <p>我们可以使用这三种方式进行优化:</p> <p><strong>隐藏元素，应用修改，重新显示</strong></p> <p>这个会在展示和隐藏节点的时候，产生两次重绘</p> <div class="language-javascript extra-class"><pre class="language-javascript"><code><span class="token keyword">function</span> <span class="token function">appendDataToElement</span><span class="token punctuation">(</span><span class="token parameter">appendToElement<span class="token punctuation">,</span> data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">let</span> li<span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> data<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    	li <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'li'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        li<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">'text'</span><span class="token punctuation">;</span>
        appendToElement<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>li<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">const</span> ul <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'list'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ul<span class="token punctuation">.</span>style<span class="token punctuation">.</span>display <span class="token operator">=</span> <span class="token string">'none'</span><span class="token punctuation">;</span>
<span class="token function">appendDataToElement</span><span class="token punctuation">(</span>ul<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">;</span>
ul<span class="token punctuation">.</span>style<span class="token punctuation">.</span>display <span class="token operator">=</span> <span class="token string">'block'</span><span class="token punctuation">;</span>
</code></pre></div><p><strong>使用文档片段(document fragment)在当前DOM之外构建一个子树，再把它拷贝回文档</strong></p> <div class="language-javascript extra-class"><pre class="language-javascript"><code><span class="token keyword">const</span> ul <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'list'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> fragment <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createDocumentFragment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">appendDataToElement</span><span class="token punctuation">(</span>fragment<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">;</span>
ul<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>fragment<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p><strong>将原始元素拷贝到一个脱离文档的节点中，修改节点后，再替换原始的元素。</strong></p> <div class="language-javascript extra-class"><pre class="language-javascript"><code><span class="token keyword">const</span> ul <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'list'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> clone <span class="token operator">=</span> ul<span class="token punctuation">.</span><span class="token function">cloneNode</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">appendDataToElement</span><span class="token punctuation">(</span>clone<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">;</span>
ul<span class="token punctuation">.</span>parentNode<span class="token punctuation">.</span><span class="token function">replaceChild</span><span class="token punctuation">(</span>clone<span class="token punctuation">,</span> ul<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>对于上述那种情况，我写了一个<a href="https://chenjigeng.github.io/example/share/%E9%81%BF%E5%85%8D%E5%9B%9E%E6%B5%81%E9%87%8D%E7%BB%98/%E6%89%B9%E9%87%8F%E4%BF%AE%E6%94%B9DOM.html" target="_blank" rel="noopener noreferrer">demo<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>来测试修改前和修改后的性能。然而实验结果不是很理想。</p> <p><strong>原因：原因其实上面也说过了，浏览器会使用队列来储存多次修改，进行优化，所以对这个优化方案，我们其实不用优先考虑。</strong></p> <h3 id="避免触发同步布局事件"><a href="#避免触发同步布局事件" class="header-anchor">#</a> 避免触发同步布局事件</h3> <p>上文我们说过，当我们访问元素的一些属性的时候，会导致浏览器强制清空队列，进行强制同步布局。举个例子，比如说我们想将一个p标签数组的宽度赋值为一个元素的宽度，我们可能写出这样的代码：</p> <div class="language-javascript extra-class"><pre class="language-javascript"><code><span class="token keyword">function</span> <span class="token function">initP</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> paragraphs<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        paragraphs<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>style<span class="token punctuation">.</span>width <span class="token operator">=</span> box<span class="token punctuation">.</span>offsetWidth <span class="token operator">+</span> <span class="token string">'px'</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>这段代码看上去是没有什么问题，可是其实会造成很大的性能问题。在每次循环的时候，都读取了box的一个offsetWidth属性值，然后利用它来更新p标签的width属性。这就导致了每一次循环的时候，浏览器都必须先使上一次循环中的样式更新操作生效，才能响应本次循环的样式读取操作。每一次循环都会强制浏览器刷新队列。我们可以优化为:</p> <div class="language-javascript extra-class"><pre class="language-javascript"><code><span class="token keyword">const</span> width <span class="token operator">=</span> box<span class="token punctuation">.</span>offsetWidth<span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">initP</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> paragraphs<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        paragraphs<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>style<span class="token punctuation">.</span>width <span class="token operator">=</span> width <span class="token operator">+</span> <span class="token string">'px'</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>同样，我也写了个<a href="https://chenjigeng.github.io/example/share/%E9%81%BF%E5%85%8D%E5%9B%9E%E6%B5%81%E9%87%8D%E7%BB%98/%E9%81%BF%E5%85%8D%E5%BF%AB%E9%80%9F%E8%BF%9E%E7%BB%AD%E7%9A%84%E5%B8%83%E5%B1%80.html" target="_blank" rel="noopener noreferrer">demo<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>来比较两者的性能差异。你可以自己点开这个demo体验下。这个对比差距就比较明显。</p> <h3 id="对于复杂动画效果-使用绝对定位让其脱离文档流"><a href="#对于复杂动画效果-使用绝对定位让其脱离文档流" class="header-anchor">#</a> 对于复杂动画效果,使用绝对定位让其脱离文档流</h3> <p>对于复杂动画效果，由于会经常的引起回流重绘，因此，我们可以使用绝对定位，让它脱离文档流。否则会引起父元素以及后续元素频繁的回流。这个我们就直接上个<a href="https://chenjigeng.github.io/example/share/%E9%81%BF%E5%85%8D%E5%9B%9E%E6%B5%81%E9%87%8D%E7%BB%98/%E5%B0%86%E5%A4%8D%E6%9D%82%E5%8A%A8%E7%94%BB%E6%B5%AE%E5%8A%A8%E5%8C%96.html" target="_blank" rel="noopener noreferrer">例子<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>。</p> <p>打开这个例子后，我们可以打开控制台，控制台上会输出当前的帧数(虽然不准)。
<img src="/assets/img/example_demo_animation.8b6d696e.png" alt="">
从上图中，我们可以看到，帧数一直都没到60。这个时候，只要我们点击一下那个按钮，把这个元素设置为绝对定位，帧数就可以稳定60。</p> <h3 id="css3硬件加速（gpu加速）"><a href="#css3硬件加速（gpu加速）" class="header-anchor">#</a> css3硬件加速（GPU加速）</h3> <p>比起考虑如何减少回流重绘，我们更期望的是，根本不要回流重绘。这个时候，css3硬件加速就闪亮登场啦！！</p> <p><strong>划重点：使用css3硬件加速，可以让transform、opacity、filters这些动画不会引起回流重绘 。但是对于动画的其它属性，比如background-color这些，还是会引起回流重绘的，不过它还是可以提升这些动画的性能。</strong></p> <p>本篇文章只讨论如何使用，暂不考虑其原理，之后有空会另外开篇文章说明。</p> <h4 id="如何使用"><a href="#如何使用" class="header-anchor">#</a> 如何使用</h4> <p>常见的触发硬件加速的css属性：</p> <ul><li>transform</li> <li>opacity</li> <li>filters</li> <li>Will-change</li></ul> <h4 id="效果"><a href="#效果" class="header-anchor">#</a> 效果</h4> <p>我们可以先看个<a href="https://chenjigeng.github.io/example/share/%E5%AF%B9%E6%AF%94gpu%E5%8A%A0%E9%80%9F/gpu%E5%8A%A0%E9%80%9F-transform.html" target="_blank" rel="noopener noreferrer">例子<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>。我通过使用chrome的Performance捕获了一段时间的回流重绘情况，实际结果如下图：</p> <p><img src="" alt=""></p> <p>从图中我们可以看出，在动画进行的时候，没有发生任何的回流重绘。如果感兴趣你也可以自己做下实验。</p> <h4 id="重点"><a href="#重点" class="header-anchor">#</a> 重点</h4> <ul><li>使用css3硬件加速，可以让transform、opacity、filters这些动画不会引起回流重绘</li> <li>对于动画的其它属性，比如background-color这些，还是会引起回流重绘的，不过它还是可以提升这些动画的性能。</li></ul> <h4 id="css3硬件加速的坑"><a href="#css3硬件加速的坑" class="header-anchor">#</a> css3硬件加速的坑</h4> <ul><li>如果你为太多元素使用css3硬件加速，会导致内存占用较大，会有性能问题。</li> <li>在GPU渲染字体会导致抗锯齿无效。这是因为GPU和CPU的算法不同。因此如果你不在动画结束的时候关闭硬件加速，会产生字体模糊。</li></ul> <h2 id="总结"><a href="#总结" class="header-anchor">#</a> 总结</h2> <p>本文主要讲了浏览器的渲染过程、浏览器的优化机制以及如何减少甚至避免回流和重绘，希望可以帮助大家更好的理解回流重绘。</p> <h2 id="参考文献"><a href="#参考文献" class="header-anchor">#</a> 参考文献</h2> <ul><li><a href="https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=zh-cn" target="_blank" rel="noopener noreferrer">渲染树构建、布局及绘制<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></li> <li>高性能Javascript</li></ul> <br> <!----></div> <footer class="page-edit"><!----> <!----></footer> <!----> </main></div><div class="global-ui"></div></div>
    <script src="/assets/js/app.1c226385.js" defer></script><script src="/assets/js/2.5bd328db.js" defer></script><script src="/assets/js/3.90e623d3.js" defer></script><script src="/assets/js/6.db1982cd.js" defer></script>
  </body>
</html>
